﻿/* ------------------------------------------------------------------------ */
/* TheNewCoders Scripting (TNCScripting) v1.0                               */
/* ------------------------------------------------------------------------ */
/* A scripting engine for C# that is even compatible with XNA for Xbox360.  */
/* Copyright (C) 2010 Prageeth Silva <prageeth@thenewcoders.com>            */
/*                                                                          */
/* This program is free software; you can redistribute it and/or            */
/* modify it under the terms of the GNU General Public License              */
/* as published by the Free Software Foundation; either version 2           */
/* of the License, or (at your option) any later version.                   */
/*                                                                          */
/* This program is distributed in the hope that it will be useful,          */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
/* GNU General Public License for more details.                             */
/*                                                                          */
/* You should have received a copy of the GNU General Public License        */
/* along with this program; if not, write to the Free Software              */
/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,               */
/* MA  02110-1301, USA. OR visit <http://www.gnu.org/licenses/gpl-2.0.html> */
/* ------------------------------------------------------------------------ */

using System;
using System.Collections.Generic;

namespace TNCScripting
{

    /// <summary>
    /// Defines the structure that stores the returned value after a script functoin has been evaluated.
    /// </summary>
    public struct ReturnValue
    {
        /// <summary>
        /// Determines if the function actually returned a value.
        /// </summary>
        public bool Valid;

        /// <summary>
        /// The returned object/value, if the function actually returned anything.
        /// </summary>
        public object Object;

        /// <summary>
        /// Creates a new ReturnValue object.
        /// </summary>
        /// <param name="valid">Whether the object stored is a valid returned object.</param>
        /// <param name="obj">The returned object.</param>
        internal ReturnValue(bool valid, object obj)
        {
            this.Valid = valid;
            this.Object = obj;
        }

    }

    /// <summary>
    /// Defines a Script structure that is used for loading scripts and executing functions
    /// </summary>
    public class Script
    {

        #region "Properties"

        private string file = string.Empty;
        /// <summary>
        /// The path to the script file.
        /// </summary>
        /// <remarks>Gets the file path set when creating the Script.</remarks>
        public string File
        {
            get { return this.file; }
        }

        private bool loaded = false;
        /// <summary>
        /// Determines if the associated script has been loaded.
        /// </summary>
        /// <remarks>Gets a boolean value stating whether the scripting has been already loaded or not.</remarks>
        public bool FileLoaded
        {
            get { return this.loaded; }
        }

        #endregion

        #region "Variables"

        // A list of Functions that are extracted from the script at loading time.
        private Dictionary<string, ScriptFunction> Functions = null;

        private char[] separators = new char[] { '.', '(', '[', ',', ')', ';', '{', '}' };
        private char[] seps = new char[] { '.' };

        #endregion

        #region "Contructors"

        /// <summary>
        /// Defines the Script structure.
        /// </summary>
        /// <param name="file">The path of the script file.</param>
        public Script(string file)
        {
            this.file = file;
        }

        #endregion

        #region "Methods"

        /// <summary>
        /// Loads the script that was specified when the Script structure was created.
        /// </summary>
        public void LoadFile()
        {
            this.LoadFile(this.file);
        }

        /// <summary>
        /// Loads the specified script file.
        /// </summary>
        /// <param name="file">The script file to load.</param>
        private void LoadFile(string file)
        {

            string path = file;

            if (!System.IO.File.Exists(path)) { throw new Exception(string.Format("Script file '{0}' missing.", path)); }

            List<List<string>> fs = ScriptFunction.GetFuncLines(System.IO.File.ReadAllLines(path), path);
            this.Functions = new Dictionary<string, ScriptFunction>(fs.Count);
            ScriptFunction scfn = null;
            foreach (List<string> list in fs)
            {
                scfn = new ScriptFunction(this, list);
                if (this.Functions.ContainsKey(scfn.Name))
                {
                    throw new Exception(string.Format("Syntax error; the duplicate function name '{0}'.", scfn.Name));
                }
                this.Functions.Add(scfn.Name, scfn);
            }
            this.loaded = true;

        }

        /// <summary>
        /// Evaluates a specific function present in the loaded script.
        /// </summary>
        /// <param name="name">The name of function; is case sensitive.</param>
        /// <param name="parameters">The parameters to be passed into the function; number (variable) of parameters should match the number of parameters specified in the script.</param>
        /// <returns>The returned object; ReturnValue.Valid indicates if anything was returned by the function and ReturnValue.Object refers to the refered object itself.</returns>
        public ReturnValue EvaluateFunction(string name, params object[] parameters)
        {

            if (!this.loaded) { throw new Exception("The script has not yet been loaded."); }

            name = name.Trim();
            ScriptFunction f = null;
            if (this.Functions.TryGetValue(name, out f))
            {
                if (f.Valid)
                {
                    if (f.Args.Count <= parameters.Length)
                    {
                        f.InitializeArgs(parameters);
                        foreach (Cmd cmd in f.Commands)
                        {
                            if (f.Returned) { break; }
                            if (cmd.Execute) { cmd.Eval(f); }
                        }
                        return new ReturnValue(true, f.ReturnValue);
                    }
                    else
                    {
                        throw new Exception(string.Format("Mismatch in the number of arguments passed into '{0}'.", name));
                    }
                }
                else
                {
                    throw new Exception(string.Format("Syntax error in function '{0}', please check the script.", name));
                }
            }
            else
            {
                throw new Exception(string.Format("The function '{0}' does not exist in the loaded script.", name));
            }

        }

        #endregion

    }

}